// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include <inttypes.h>
#include <lib/cmdline.h>
#include <lib/crypto/entropy/collector.h>
#include <lib/crypto/entropy/hw_rng_collector.h>
#include <lib/crypto/entropy/jitterentropy_collector.h>
#include <lib/crypto/entropy/quality_test.h>
#include <platform.h>
#include <string.h>
#include <zircon/types.h>

#include <dev/hw_rng.h>
#include <lk/init.h>
#include <vm/vm_object_paged.h>

namespace crypto {

namespace entropy {

#if ENABLE_ENTROPY_COLLECTOR_TEST

#ifndef ENTROPY_COLLECTOR_TEST_MAXLEN
#define ENTROPY_COLLECTOR_TEST_MAXLEN (1024u * 1024u)
#endif

namespace {

uint8_t entropy_buf[ENTROPY_COLLECTOR_TEST_MAXLEN];
size_t entropy_len;

}  // namespace

fbl::RefPtr<VmObject> entropy_vmo;
bool entropy_was_lost = false;

static void SetupEntropyVmo(uint level) {
  if (VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, 0u, entropy_len, &entropy_vmo) != ZX_OK) {
    printf("entropy-boot-test: Failed to create entropy_vmo (data lost)\n");
    entropy_was_lost = true;
    return;
  }
  size_t actual;
  if (entropy_vmo->Write(entropy_buf, 0, entropy_len, &actual) != ZX_OK) {
    printf("entropy-boot-test: Failed to write to entropy_vmo (data lost)\n");
    entropy_was_lost = true;
    return;
  }
  if (actual < entropy_len) {
    printf("entropy-boot-test: partial write to entropy_vmo (data lost)\n");
    entropy_was_lost = true;
    return;
  }
  constexpr const char* name = "debug/entropy.bin";
  if (entropy_vmo->set_name(name, strlen(name)) != ZX_OK) {
    // The name is needed because devmgr uses it to add the VMO as a file in
    // the /boot filesystem.
    printf("entropy-boot-test: could not name entropy_vmo (data lost)\n");
    entropy_was_lost = true;
    return;
  }
}

// Run the entropy collector test.
void EarlyBootTest() {
  const char* src_name = gCmdline.GetString("kernel.entropy-test.src");
  if (!src_name) {
    src_name = "";
  }

  entropy::Collector* collector = nullptr;
  entropy::Collector* candidate;
  char candidate_name[ZX_MAX_NAME_LEN];

  // TODO(andrewkrieger): find a nicer way to enumerate all entropy collectors
  if (HwRngCollector::GetInstance(&candidate) == ZX_OK) {
    candidate->get_name(candidate_name, sizeof(candidate_name));
    if (strncmp(candidate_name, src_name, ZX_MAX_NAME_LEN) == 0) {
      collector = candidate;
    }
  }
  if (!collector && JitterentropyCollector::GetInstance(&candidate) == ZX_OK) {
    candidate->get_name(candidate_name, sizeof(candidate_name));
    if (strncmp(candidate_name, src_name, ZX_MAX_NAME_LEN) == 0) {
      collector = candidate;
    }
  }

  // TODO(andrewkrieger): add other entropy collectors.

  if (!collector) {
    printf("entropy-boot-test: unrecognized source \"%s\"\n", src_name);
    printf("entropy-boot-test: skipping test.\n");
    return;
  }

  entropy_len = gCmdline.GetUInt64("kernel.entropy-test.len", sizeof(entropy_buf));
  if (entropy_len > sizeof(entropy_buf)) {
    entropy_len = sizeof(entropy_buf);
    printf(
        "entropy-boot-test: only recording %zu bytes (try defining "
        "ENTROPY_COLLECTOR_TEST_MAXLEN)\n",
        sizeof(entropy_buf));
  }

  zx_time_t start = current_time();
  size_t result = collector->DrawEntropy(entropy_buf, entropy_len);
  zx_time_t end = current_time();

  if (result < entropy_len) {
    printf("entropy-boot-test: source only returned %zu bytes.\n", result);
    entropy_len = result;
  } else {
    printf("entropy-boot-test: successful draw in %" PRIu64 " nanoseconds.\n", end - start);
  }
}

#else  // ENABLE_ENTROPY_COLLECTOR_TEST

void EarlyBootTest() {}

#endif  // ENABLE_ENTROPY_COLLECTOR_TEST

}  // namespace entropy

}  // namespace crypto

#if ENABLE_ENTROPY_COLLECTOR_TEST
LK_INIT_HOOK(setup_entropy_vmo, crypto::entropy::SetupEntropyVmo, LK_INIT_LEVEL_VM + 1);
#endif
